﻿using System.Security.Claims;
using MicroBlog.Areas.Admin.ViewModels;
using MicroBlog.Data;
using MicroBlog.Infrastructure;
using MicroBlog.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.EntityFrameworkCore;

namespace MicroBlog.Services
{
    public class UserManager : IUserManager
    {
        private readonly MicroBlogContext _context;

        public UserManager(MicroBlogContext context)
        {
            _context = context;
        }
        public async Task<User> FindByNameAsync(string userName)
        {
            return await _context.Users.FirstOrDefaultAsync(w => w.Name == userName);
        }
        public async Task<ContextResult> CreateAsync(string userName, string secret)
        {
            User user = new User
            {
                Name = userName,
                Created = DateTime.Now
            };
            if (!string.IsNullOrEmpty(secret))
            {
                byte[] salt = Pbkdf2Hasher.GenerateRandomSalt();
                string hash = Pbkdf2Hasher.ComputeHash(secret, salt);

                user.Secret = hash;
                user.Extra = Convert.ToBase64String(salt);
            }
            try
            {
                _context.Users.Add(user);
                await _context.SaveChangesAsync();
            }
            catch (Exception ex)
            {
                throw new Exception("创建用户失败：" + ex.Message, ex);
            }
            var result = ContextResult.Success;
            result.Message = user.Id.ToString();
            return result;
        }
        public async Task<ContextResult> CreateRoleAsync(Role role)
        {
            _context.Roles.Add(role);
            await _context.SaveChangesAsync();
            return ContextResult.Success;
        }
        public async Task<ContextResult> AddToRole(int userId, string roleName)
        {
            Role role = await _context.Roles.Where(r => string.Equals(r.Name, roleName, StringComparison.OrdinalIgnoreCase)).FirstOrDefaultAsync();

            if (role == null)
            {
                return ContextResult.Failed(ErrorDescriber.RoleNotFound(roleName));
            }

            var userRole = new UserRole
            {
                UserId = userId,
                RoleId = role.Id
            };
            _context.UserRoles.Add(userRole);
            await _context.SaveChangesAsync();
            return ContextResult.Success;
        }
        public async Task<ContextResult> RemoveAsync(int id)
        {
            var user = await _context.Users.Where(a => a.Id == id).FirstOrDefaultAsync();
            if (user == null)
            {
                return ContextResult.Failed(ErrorDescriber.UserNotFound());
            }
            _context.Users.Remove(user);
            await _context.SaveChangesAsync();
            return ContextResult.Success;
        }
        public async Task<ContextResult> ChangeSecret(int userId, string oldSecret, string secret)
        {
            var user = await _context.Users.Where(a => a.Id == userId).FirstOrDefaultAsync();
            if (user == null)
            {
                return ContextResult.Failed(ErrorDescriber.UserNotFound());
            }
            byte[] salt = Convert.FromBase64String(user.Extra);
            string hash = Pbkdf2Hasher.ComputeHash(oldSecret, salt);

            if (user.Secret != hash)
                return ContextResult.Failed(ErrorDescriber.PasswordMismatch());

            byte[] newSalt = Pbkdf2Hasher.GenerateRandomSalt();
            string newShash = Pbkdf2Hasher.ComputeHash(secret, newSalt);

            user.Secret = newShash;
            user.Extra = Convert.ToBase64String(newSalt);
            _context.Users.Update(user);
            await _context.SaveChangesAsync();
            return ContextResult.Success;
        }

        public async Task<ContextResult> SignInAsync(HttpContext httpContext, string userName, string secret, bool isPersistent = false)
        {
            var user = await _context.Users.Where(a => a.Name == userName).FirstOrDefaultAsync();
            if (user == null)
            {
                return ContextResult.Failed(ErrorDescriber.UserNotFound());
            }
            byte[] salt = Convert.FromBase64String(user.Extra);
            string hash = Pbkdf2Hasher.ComputeHash(secret, salt);

            if (user.Secret != hash)
                return ContextResult.Failed(ErrorDescriber.LoginFailed());

            return await SignInAsync(httpContext, user, isPersistent);
        }

        public async Task SignOutAsync(HttpContext httpContext)
        {
            await httpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        }

        public int GetCurrentUserId(HttpContext httpContext)
        {
            if (!httpContext.User.Identity.IsAuthenticated)
                return -1;

            Claim claim = httpContext.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);

            if (claim == null)
                return -1;

            if (!int.TryParse(claim.Value, out int currentUserId))
            {
                currentUserId = -1;
            }

            return currentUserId;
        }

        public async Task<UserModel> GetCurrentUser(HttpContext httpContext)
        {
            int currentUserId = GetCurrentUserId(httpContext);

            if (currentUserId == -1)
                return null;
            var query = from u in _context.Users.Where(w => w.Id == currentUserId)
                        select new UserModel
                        {
                            UserId = u.Id,
                            OldSecret = "",
                            Secret = ""
                        };
            return await query.FirstOrDefaultAsync();
        }
        /// <summary>
        /// 根据用户信息生成验证票，并写入cookie，完成登录操作
        /// </summary>
        /// <param name="httpContext"></param>
        /// <param name="user"></param>
        /// <param name="isPersistent"></param>
        /// <returns></returns>
        private async Task<ContextResult> SignInAsync(HttpContext httpContext, User user, bool isPersistent = false)
        {
            //获取用户的声明/凭证信息
            var userClaims = await GetUserClaims(user);

            ClaimsIdentity identity = new ClaimsIdentity(userClaims, CookieAuthenticationDefaults.AuthenticationScheme);
            ClaimsPrincipal principal = new ClaimsPrincipal(identity);

            await httpContext.SignInAsync(
              CookieAuthenticationDefaults.AuthenticationScheme,
              principal,
              new AuthenticationProperties()
              {
                  IsPersistent = isPersistent,
                  ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(App.AuthExpireTime),
                  AllowRefresh = true
              });

            return ContextResult.Success;
        }
        /// <summary>
        /// 获取用户的所有声明/凭证信息
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        private async Task<IEnumerable<Claim>> GetUserClaims(User user)
        {
            List<Claim> claims = new List<Claim>
            {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Name)
            };
            claims.AddRange(await GetUserRoleClaims(user));
            return claims;
        }
        /// <summary>
        /// 获取用户所属的角色
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        private async Task<IEnumerable<Claim>> GetUserRoleClaims(User user)
        {
            List<Claim> claims = new List<Claim>();
            var query = from ur in _context.UserRoles.Where(ur => ur.UserId == user.Id)
                        join role in _context.Roles on ur.RoleId equals role.Id
                        select new
                        {
                            role.Id,
                            role.Name
                        };
            var roles = await query.ToListAsync();
            if (roles != null)
            {
                var roleIds = roles.Select(rp => rp.Id).ToList();
                var permissions = await GetRolePermissionClaims(roleIds);

                foreach (var role in roles)
                {
                    claims.Add(new Claim(ClaimTypes.Role, role.Name));
                }
            }

            return claims;
        }
        /// <summary>
        /// 获取角色的权限
        /// </summary>
        /// <param name="role"></param>
        /// <returns></returns>
        private async Task<IEnumerable<object>> GetRolePermissionClaims(IList<int> roleIds)
        {
            var query = from rp in _context.RolePermissions.Where(rp => roleIds.Contains(rp.RoleId))
                        join p in _context.Permissions on rp.PermissionId equals p.Id
                        select new
                        {
                            p.Module,
                            p.Controller,
                            p.Action
                        };
            return await query.ToListAsync();

        }

    }
}